﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace TransportLayer.Manager
{
    public delegate void NodeRemoveHandler(int nodeID);
    public delegate void LinkRemoveHandler(List<int> linksToDeleteList);
    public class Engine
    {
        public class Node
        {
            private int id;
            private string type;
            private string name;
            private float positionX;
            private float positionY;
            private float delay;
            private bool accessible;
            private Process process;

            public Process Process
            {
                get { return process; }
                set { process = value; }
            }

            public int Id
            {
                get { return id; }
                set { id = value; }
            }
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
            public string Type
            {
                get { return type; }
                set { type = value; }
            }

            public float PositionX
            {
                get { return positionX; }
                set { positionX = value; }
            }

            public float PositionY
            {
                get { return positionY; }
                set { positionY = value; }
            }
            public float Delay
            {
                get { return delay; }
                set { delay = value; }
            }
            public bool Accessible
            {
                get { return accessible; }
                set { accessible = value; }
            }

            public Socket Socket;

            public void KillProcess()
            { 
                try{
                    process.Kill();
                }catch(Exception){}
            }
            //public Topology.Node tnode;
        }


         public class Link
         {
             private int id;

             private Node sourceNode;
             private Node targetNode;

             private int sourcePort;
             private int targetPort;

             //paramiters
             private float delay;
             private float errorRate;
             private int capacity;

             public float Delay { get { return delay; } }
             public float ErrorRate { get { return errorRate; } }
             public int Capacity { get { return capacity;  } }

             public int Id
             {
                 get { return id; }
                 set { id = value; }
             }

             public int SourcePort
             {
                 get { return sourcePort; }
             }

             public int TargetPort
             {
                 get { return targetPort;  }
             }

             public Node SourceNode 
             {
                 get { return sourceNode; }
             }
             public Node TargetNode
             {
                 get { return targetNode; }
             }

             public Link(int id, Node sourceNode, Node targetNode, int sourcePort, int targetPort, int capacity, float delay, float errorRate)
             {
                 this.id = id;
                 this.sourceNode = sourceNode;
                 this.targetNode = targetNode;
                 this.sourcePort = sourcePort;
                 this.targetPort = targetPort;
                 this.capacity = capacity;
                 this.errorRate = errorRate;
                 this.delay = delay;
             }
         
         }

        public class NodeUnaccessibleException : Exception
        {
            public int Id = -1;
            public NodeUnaccessibleException()
            { }
            public NodeUnaccessibleException(int id)
            {
                Id = id;
            }
        }

        public event NodeRemoveHandler nodeRemoveHandler;
        public event LinkRemoveHandler linkRemoveHandler;

        public Dictionary<int, Node> nodes = new Dictionary<int, Node>(); // mapa numerów id na metadane węzłów

        //<NodeId, NodeStorageInformation>
        public Dictionary<int, Node> Nodes { get { return nodes; } }

        private Dictionary<int, Link> links = new Dictionary<int, Link>();
        public Dictionary<int, Link> Links { get { return links; } }
        //connectionID
        private Dictionary<int, NetworkConnection> connections = new Dictionary<int, NetworkConnection>();
        public Dictionary<int, NetworkConnection> Connections
        { get { return connections; } }

        public string NetworkName { get; set; }

        private Socket socket;

        Random random = new Random();
        private bool UpdateNodeProcesses = false;
        private Thread pingTheard = null;
        private const int timeKeepAlive = 1000;


        
    

        public int Port
        {
            get
            {
                if (this.socket != null)
                    return ((IPEndPoint)this.socket.LocalEndPoint).Port;
                else
                    return 0;
            }
        }


        public void Reset()
        {
            foreach (Node node in nodes.Values)
            {
                node.Socket.Close();
            }
            nodes.Clear();
           // topology.Clear();
        }

        public void Init()
        {
            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ip = new IPEndPoint(IPAddress.Any, 0);
            this.socket.Bind(ip);
            this.socket.Listen(10);
            this.socket.BeginAccept(OnClientConnect, socket);
         
        }
        //delete all processes
        public void Clean()
        {
            foreach (KeyValuePair<int, Node> kvp in nodes)
            {
                kvp.Value.KillProcess();         
            }
            nodes.Clear();
            StopAlive();
        }
        //Pharse process dictonary to node dictonary
        public void PharseProcessDictonary(Dictionary<int, Process> pd) 
        {
            foreach (KeyValuePair<int, Process> kvp in pd)
            {
                if (nodes.ContainsKey(kvp.Key))
                {
                    kvp.Value.ErrorDataReceived += new DataReceivedEventHandler(Node_ErrorReceived);
                    nodes[kvp.Key].Process = kvp.Value;
                }
            }
        }


        private void Node_ErrorReceived(Object sender, DataReceivedEventArgs e)
        {
            var p = e.GetType();
        }

        private void OnClientConnect(IAsyncResult asyn)
        {
            Node node = new Node();
            node.Socket = this.socket.EndAccept(asyn);
            string idStr;
            try
            {
                idStr = Get(node.Socket, "id");
                node.Id = Int32.Parse(idStr);
                node.Name = Get(node.Socket, "name");
                node.Type = Get(node.Socket, "type");
                node.PositionX = Int32.Parse(Get(node.Socket, "x"));
                node.PositionY = Int32.Parse(Get(node.Socket, "y"));
                node.Accessible = Boolean.Parse(Get(node.Socket, "accessible"));
                if (node.Type == "switch" || node.Type == "matrix") 
                    node.Delay = float.Parse(Get(node.Socket, "delay"));
            }
            catch (NodeUnaccessibleException)
            {
                this.socket.BeginAccept(OnClientConnect, socket);
                return;
            }
            
            
            nodes.Add(node.Id, node);
            this.socket.BeginAccept(OnClientConnect, socket);
        }

        private string Query(Socket sock, string query)
        {
            query = query.ToLower();
            try
            {
                sock.Send(Encoding.ASCII.GetBytes(query));
                byte[] buffer = new byte[4096];
                int received = sock.Receive(buffer);
                return Encoding.ASCII.GetString(buffer, 0, received).ToLower();
            }
            catch (SocketException) { throw new NodeUnaccessibleException(); }
        }

        public string Query(int id, string query)
        {
            query = query.ToLower();
            if (nodes.ContainsKey(id))
            {
                try
                {
                    nodes[id].Socket.Send(Encoding.ASCII.GetBytes(query));
                    byte[] buffer = new byte[4096];
                    int received = nodes[id].Socket.Receive(buffer);
                    return Encoding.ASCII.GetString(buffer, 0, received).ToLower();
                }
                catch (SocketException)
                {
                        nodes[id].KillProcess();
                        nodes.Remove(id);
                        List<int> diconectedLinks= new List<int>();
                        foreach (KeyValuePair<int, Link> kvp in links)
                        {
                            if (kvp.Value.SourceNode.Id == id)
                            {
                                diconectedLinks.Add(kvp.Value.Id);
                            }
                            if (kvp.Value.TargetNode.Id == id)
                            {
                                diconectedLinks.Add(kvp.Value.Id);
                            }
                        }
                        foreach (int id_l in diconectedLinks)
                            links.Remove(id_l); //removing

                        if (nodeRemoveHandler != null)
                            nodeRemoveHandler(id);

                        if (linkRemoveHandler != null)
                            linkRemoveHandler(diconectedLinks);
                    }
                    
            }
            return "";
        }



        private string Get(Socket sock, string param)
        {
            param = param.ToLower();
            string response = Query(sock, "get " + param);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 3 && tokens[0] == "getresp" && tokens[1] == param)
                return tokens[2];
            return "";
        }

        //uzywac tego jesli mozna
        private string Get(int id, string param)
        {
            param = param.ToLower();
            string response = Query(id, "get " + param);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 3 && tokens[0] == "getresp" && tokens[1] == param)
                return tokens[2];
            return "";
        }

        private string Set(int id, string param, string value)
        {
            string response = Query(id, "set " + param + " " + value);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 3 && tokens[0] == "setresp" && tokens[1] == param)
                return tokens[2];
            return "";
        }

        // pobranie listy dostepnych elementow
        public List<string> GetElements()
        {
            List<string> ret = new List<string>();
            int[] keys = new int[nodes.Count];
            nodes.Keys.CopyTo(keys, 0);
            Array.Sort(keys);
            foreach (int key in keys)
            {
                if (Ping(key))
                {
                    string item = "[" + key + "] " + nodes[key].Name;
                    ret.Add(item);
                }
            }
            return ret;
        }
        //sprawdza co jakis czas czy nody dalej żyją.
        public void KeepAlive()
        {
            UpdateNodeProcesses = true;
            pingTheard = new Thread(CheckNodesAreAlive);
            pingTheard.Start();
        }
        public void StopAlive()        {
            UpdateNodeProcesses = false;
        }
        private void CheckNodesAreAlive()
        {
             while (UpdateNodeProcesses)
             {
                
                 try{
                     foreach (int id in nodes.Keys)
                     {
                         this.Ping(id);
                     }
                    }catch(InvalidOperationException){}
                 
                Thread.Sleep(timeKeepAlive);
            }
        }

        private void CheckLinksAreAlive()
        {


        }
   //     public List<string> GetLinks()
  //      {
   //         List<string> ret = new List<string>();
   //         foreach (var link in topology.Edges)
   //         {
    //            string item = "{[" + link.Source.Id + "]" + link.Source.Name + "/" + link.SourcePort +
   //                 "} -> {[" + link.Target.Id + "]" + link.Target.Name + "/" + link.TargetPort + "} -- " + link.Capacity;
    //            ret.Add(item);
    //        }
    //        return ret;
    //    }


        public List<string> GetConnections()
        {
            List<string> ret = new List<string>();
            int[] keys = new int[connections.Count];
            connections.Keys.CopyTo(keys, 0);
            Array.Sort(keys);
            foreach (int key in keys)
            {
                string item = "[" + key + "] " + nodes[connections[key].Source].Name +  " ---> " + nodes[connections[key].Target].Name;
                ret.Add(item);
            }
            return ret;
        }



        public bool Ping(int id)
        {
            string pong = Query(id, "ping");
            if (pong == "ping")
                return true;
            else 
                return false;
        }

        public int ConnectedNodes
        {
            get { return nodes.Count; }
        }

        public Log GetLog(int id, int index)
        {
            string response = Query(id, "get log " + index);
            if (response != "")
                return (Log)TransportLayer.NodeToolsLibrary.Serial.DeserializeObject(response, typeof(Log));
            else
                return null;
        }



        public string GetRouting(int id)
        {
            string response = Query(id, "get routing");
            if (response != "")
                return response;
            else
                return null;
        }


      
        //label=portIn>portOut connectionID
        byte[] buffer = new byte[4096];
        public bool AddForwardingEntry(int idIn,int idOut,string label, int connectionID)
        {
                label = label + " " + connectionID;
                int received;
                string response = "fail";
                lock (this)
                {
                    while (true)
                    {
                        if (nodes.ContainsKey(idIn) && nodes.ContainsKey(idOut))
                        {
                            if (!AddForwardingEntry(idIn, label))
                                return false;
                        }
                        else return false;

                        received = nodes[idOut].Socket.Receive(buffer);
                        response = Encoding.ASCII.GetString(buffer, 0, received);
                        if (response == "ok")
                            break;
                    }
                }
                return true;
        }

        public bool AddForwardingEntry(int id, string label)
        {
            string response = Query(id, "rtadd " + label);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 2 && tokens[1] == "ok")
                return true;
            else
                return false;
        }


        public bool RemoveForwardingEntry(int id, string label)
        {
            string response = Query(id, "rtdel " + label);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 3 && tokens[2] == "ok")
                return true;
            return false;
        }

        public bool RemoveForwardingEntry(int nodeId, int connectionId)
        {
            string response = Query(nodeId, "rtdel " + connectionId);
            string[] tokens = response.Split(' ');
            if (tokens.Length == 3 && tokens[2] == "ok")
                return true;
            return false;
        }

        public int AddLink(TransportLayer.Link link)
        {
            if (nodes.ContainsKey(link.StartNode) && nodes.ContainsKey(link.EndNode))
            {
                string port = Get(link.EndNode, "PortsIn." + link.EndPort + "._port");
                Set(link.StartNode, "PortsOut." + link.StartPort + "._port", port);
                Set(link.StartNode, "PortsOut." + link.StartPort + ".Connected", "True");

                int id = random.Next();

                Link newLink = new Link(id, nodes[link.StartNode], nodes[link.EndNode], link.StartPort, link.EndPort,link.Capacity,link.Delay, link.ErrorRate);

                links.Add(newLink.Id, newLink);

                return id;
            }

            return -1;
        }

        public int GetNodeFreeID()
        {
            int i = 1;
            while (nodes.ContainsKey(i))
                i++;
            return i;
        }

        public int GetFreeId()
        {
            int i = 1;
            while (connections.ContainsKey(i))
                i++;
            return i;
        }



        public void AddUserConnection(NetworkConnection connection)
        {
            connections.Add(connection.Id, connection);
        }

        public bool SetupUserConnection(int connectionId)
        {
            if (!connections.ContainsKey(connectionId))
                return false;
            NetworkConnection connection = connections[connectionId];
            if (!connections[connectionId].Active)
            {

                string lastLabel = "";
                foreach (var link in connection.Path)
                {
                    
                    if (!AddForwardingEntry(link.SourceId, link.TargetId, lastLabel +">"+link.SourceRouting,connectionId))
                    {   // to nie powinno się zdarzyć...
                        Disconnect(connectionId);
                        return false;
                    }
                   lastLabel = link.TargetRouting;
                    
                }
             connection.Active = true;
            }
            return connection.Active;
        }

        public void Disconnect(int connectionId)
        {
            NetworkConnection connection = connections[connectionId];
            foreach (var link in connection.Path)
            {
                RemoveForwardingEntry(link.SourceId, connection.Id);
                    
            }
            RemoveForwardingEntry(connection.Target, connection.Id);
            connections.Remove(connectionId);
        }

        public int AddSink(TransportLayer.Node sink, int connectToNode)
        {

         if (sink.Type == "Sink" && nodes.ContainsKey(connectToNode) && !nodes.ContainsKey(sink.Id))
         {
             string[] downPortsIn = GetDownPortOut(connectToNode);
             if (downPortsIn.Length == 0)
                 return -1;

                Process process = ExecuteProcess(sink, 1234);
                while (!nodes.ContainsKey(sink.Id))
                { Thread.Sleep(50); }

                nodes[sink.Id].Process = process;
                TransportLayer.Link link = new TransportLayer.Link();
                link.StartNode = connectToNode;
                link.StartPort = Int32.Parse(downPortsIn[0]);
                link.EndNode = sink.Id;
                link.EndPort = 0;

                int linkId = this.AddLink(link);
         
           return linkId;
         }
         else return -1;
        
        }

        public int AddSource(TransportLayer.Node source, int connectToNode)
        {

            if (source.Type == "Source" && nodes.ContainsKey(connectToNode) && !nodes.ContainsKey(source.Id))
            {
                string[] downPortsOut = GetDownPortIn(connectToNode);
                if (downPortsOut.Length == 0)
                    return -1;

                Process process = ExecuteProcess(source, 1234);
                while (!nodes.ContainsKey(source.Id))
                { Thread.Sleep(50); }

                nodes[source.Id].Process = process;
                TransportLayer.Link link = new TransportLayer.Link();
                link.StartNode = source.Id;
                link.StartPort = 0;
                link.EndNode = connectToNode;
                link.EndPort = Int32.Parse(downPortsOut[0]);

                int linkId = this.AddLink(link);

                return linkId;
            }
            else return -1;

        }


        private Process ExecuteProcess(TransportLayer.Node node, int ccport)
        {
            Random random = new Random();
            string tempfile = "tmp" + random.Next() + ".xml";
            node.save(tempfile);
            Process process = new Process();
            process.StartInfo.FileName = "TransportLayer.ExecNode.exe";
            process.StartInfo.Arguments = tempfile + " " + this.Port + " " + ccport;
            process.Start();
            return process;
        }

        public int GetCallerPort(int id)
        {
            string rv = this.Get(id, "callerport");

            return Int32.Parse(rv);
        }

        public int GetNodeFreeId()
        {
            int id=1;
            while (nodes.ContainsKey(id))
                id++;
            return id;
        }

        public string[] GetUpPortIn(int id)
        {
            string ports = Get(id, "upportsin");
            char[] spliter = { '#' };
            string[] portSplited = ports.Split(spliter, StringSplitOptions.RemoveEmptyEntries);
            return portSplited;
        }
        public string[] GetUpPortOut(int id)
        {
            string ports = Get(id, "upportsout");
            char[] spliter = { '#' };
            string[] portSplited = ports.Split(spliter, StringSplitOptions.RemoveEmptyEntries);
            return portSplited;
        }
        public string[] GetDownPortIn(int id)
        {
            string ports = Get(id, "downportsin");
            char[] spliter = { '#' };
            string[] portSplited = ports.Split(spliter, StringSplitOptions.RemoveEmptyEntries);
            return portSplited;
        }
        public string[] GetDownPortOut(int id)
        {
            string ports = Get(id, "downportsout");
            char[] spliter = { '#' };
            string[] portSplited = ports.Split(spliter, StringSplitOptions.RemoveEmptyEntries);
            return portSplited;
        }

        public void HideNode(int id)
        {
            this.Get(id, "hide");
        }

        public void ShowNode(int id)
        {
            this.Get(id, "show");
        }

        public void HideAllNodes()
        {
            foreach(int id in nodes.Keys)
            this.Get(id, "hide");
        }

        public TransportLayer.NodeToolsLibrary.Point GetNodePosition(int id)
        { 
            double x = nodes[id].PositionX;
            double y = nodes[id].PositionY;
            return new TransportLayer.NodeToolsLibrary.Point(x, y);
        }

    }
}